#ifndef _MAC2NAME_H__
#define _MAC2NAME_H__

#include "log.h"
#include <sqlite3.h>
#ifdef linux
#include <arpa/inet.h>
#endif
#include "uint64pool.h"
#include "list.h"


#define MAC2NAME_HOST_MAX_LEN 1024

struct mac2name {
    struct uint64pool *mac2name_pool;//   = NULL;
    struct uint64pool *mac2name_pool_old;
    uint32_t mac2name_pool_size;// = 0;
    sqlite3 *mac2name_db;// = NULL;
    int mac2name_timeout;
    time_t mac2name_timeout_last;// = 0;
    uint32_t mac2name_clear_count;// = 0;
    int load_mac2name_from_sqlite_count;// = 0;
};

static inline int mac2name_litesql3_callback(void *NotUsed, int argc, char **argv, char **azColName)
{
    int i;
    for(i=0; i<argc; i++){
        LOGERR("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    LOGERR("\n");
    return 0;
}

static inline const char *mac2name_find(struct mac2name *mac2name, uint64_t mac_l)
{
    if (mac2name) {
        struct uint64pool_userdata *userdata = uint64pool_find(mac2name->mac2name_pool, mac_l, NULL, NULL);
        if (userdata) {
            return (char *)userdata->data;
        }
    }
    return "";
}

//static inline void mac2name_add(struct mac2name *mac2name, uint64_t mac_l, char *hostname, int hostname_len, int add_to_sqlite, int verbose)
#define mac2name_add(mac2name, mac_l, hostname, hostname_len, add_to_sqlite, verbose) do { if (mac2name) { \
    const char *_hostname = hostname; int _hostname_len = hostname_len; \
    struct uint64pool_userdata *userdata = uint64pool_find(mac2name->mac2name_pool, mac_l, NULL, NULL); \
    if (userdata) { \
        if (strlen(userdata->data) != _hostname_len || memcmp(userdata->data, _hostname, _hostname_len)) { \
            if (verbose) { LLOG("update mac2name_pool %018"PRIx64" -> %-36.*s\n", mac_l, _hostname_len, _hostname); } \
            char *new = realloc(userdata->data, _hostname_len + 1); \
            if (new) { \
                userdata->data = new; \
                memcpy(userdata->data, _hostname, _hostname_len); \
                memcpy(userdata->data + _hostname_len, "", 1); \
            } else { \
                if (verbose) { LLOG("update mac2name_pool %018"PRIx64" -> %-36.*s fail[%m]\n", mac_l, _hostname_len, _hostname); } \
            } \
        /*} else { LLOG("mac %018lx hostname %.*s already in db, no need update\n", mac_l, _hostname_len, _hostname); */\
        } \
    } else { \
        char *data = malloc(_hostname_len + 1); \
        if (data) { \
            memcpy(data, _hostname, _hostname_len); \
            memcpy(data + _hostname_len, "", 1); \
            if (uint64pool_add(mac2name->mac2name_pool, mac_l, mac_l, data, 1)) { \
                if (verbose) { LLOG("add to mac2name_pool %018"PRIx64" %36.*s fail\n", mac_l, _hostname_len, _hostname); } \
                free(data); \
            } else { \
                struct uint64pool_userdata *findout_userdata = uint64pool_find(mac2name->mac2name_pool, mac_l, NULL, NULL); \
                if (verbose) { \
                    if (findout_userdata) { LOGMSG("add to mac2name_pool %018"PRIx64" %36.*s ok\n", mac_l, _hostname_len, _hostname); \
                    } else {                LOGMSG("add to mac2name_pool %018"PRIx64" %36.*s fail\n", mac_l, _hostname_len, _hostname); \
                    } \
                } \
                mac2name->mac2name_pool_size++; \
                if (add_to_sqlite) { \
                    char sql[1024]; sql[sizeof(sql)-1] = 0; \
                    snprintf(sql, sizeof(sql)-1, "insert into mac2name values(%"PRIu64", \"%.*s\")", mac_l, _hostname_len, _hostname); \
                    LOGMSG("sql %s\n", sql); \
                    if (mac2name->mac2name_db) { \
                        char *zErrMsg = 0; \
                        int ret = sqlite3_exec(mac2name->mac2name_db, sql, mac2name_litesql3_callback, 0, &zErrMsg); \
                        if(ret != SQLITE_OK) { LOGFEW("SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } \
                    } \
                } \
            } \
        } else { \
            LOGFEW("add to mac2name_pool %018"PRIx64" %36.*s fail\n", mac_l, _hostname_len, _hostname); \
        } \
    } \
} } while(0)

static inline int sqlite_load_mac2name_callback(void *NotUsed, int argc, char **argv, char **azColName)
{
    struct mac2name *mac2name = NotUsed;
    int i;
    if (argc != 2) {
        LOGFEW("error, field count is not 2\n");
        for(i=0; i<argc; i++){ LOGERR("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); }
    } else {
        if (mac2name) {
            //for(i=0; i<argc; i++){ LOGERR("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); }
            uint64_t mac_l = strtoull(argv[0], NULL, 10);
            mac2name_add(mac2name, mac_l, argv[1], strlen(argv[1]), 0, 0);
            mac2name->load_mac2name_from_sqlite_count++;
        }
    }
    return 0;
}

#define mac2name_create(mac2name, db_path, timeout) ({ \
        const char *_db_path = db_path; \
    memset((mac2name), 0, sizeof(*(mac2name))); \
    (mac2name)->mac2name_pool = uint64pool_new(); \
    (mac2name)->mac2name_timeout = timeout; \
    if (_db_path && _db_path[0]) { \
        int ret = sqlite3_open(_db_path, &(mac2name)->mac2name_db); \
        if (ret) { \
            LOGERR("Can't open database: %s [%s]\n", db_path, sqlite3_errmsg((mac2name)->mac2name_db)); \
            sqlite3_close((mac2name)->mac2name_db); \
            (mac2name)->mac2name_db = NULL; \
        } \
    } \
    if ((mac2name)->mac2name_db) { \
        char *zErrMsg = 0; \
        ret = sqlite3_exec((mac2name)->mac2name_db, "create table mac2name(mac bigint primary key, domain varchar(64))", mac2name_litesql3_callback, 0, &zErrMsg); \
        if (ret != SQLITE_OK){ \
            LOGERR("SQL error: %s\n", zErrMsg); \
            sqlite3_free(zErrMsg); \
        } \
        ret = sqlite3_exec((mac2name)->mac2name_db, "select * from mac2name", sqlite_load_mac2name_callback, mac2name, &zErrMsg); \
        if (ret != SQLITE_OK) { \
            LOGERR("load from sqlite error [%s]\n", zErrMsg); \
            sqlite3_free(zErrMsg); \
        } \
        LOGERR("load_mac2name_from_sqlite_count %u\n", (mac2name)->load_mac2name_from_sqlite_count); \
    } \
    0; })

static inline void mac2name_check_timeout(struct mac2name *mac2name)
{
    if (mac2name->mac2name_timeout > 0) {
        if (mac2name->mac2name_timeout_last == 0) mac2name->mac2name_timeout_last = time(NULL);
        uint32_t interval = time(NULL) - mac2name->mac2name_timeout_last;
        if (interval > mac2name->mac2name_timeout) {
            mac2name->mac2name_timeout_last = time(NULL);
            if (mac2name->mac2name_pool_old) uint64pool_destroy(mac2name->mac2name_pool_old, NULL);
            mac2name->mac2name_pool_old = mac2name->mac2name_pool;
            mac2name->mac2name_pool= uint64pool_new();
            mac2name->mac2name_pool_size = 0;
            mac2name->mac2name_clear_count++;
        }
    }
}

static inline int mac2name_dhcp_data_to_host(struct mac2name *mac2name, const void *__app_data, int app_data_len, char *hostname, int *hostname_buf_len, int verbose,
        void (*callback)(const char *hostname, int hostname_len, uint64_t mac_l, void *c),
        void *c, const char *caller_func, int caller_line)
{
    if (mac2name == NULL) return -1;
    const uint8_t *app_data = (const uint8_t *)__app_data;
    const uint8_t *end = app_data + app_data_len;
    const uint8_t *rest_data = app_data;
    uint64_t mac_l = 0;

    if (end - rest_data <  1) { return -1; } if (rest_data[0] != 0x01) { return -1; } rest_data += 1; //message type
    if (end - rest_data <  1) { return -1; } if (rest_data[0] != 0x01) { return -1; } rest_data += 1; //hardware type
    if (end - rest_data <  1) { return -1; } if (rest_data[0] != 0x06) { return -1; } rest_data += 1; //hardware addr len
    if (end - rest_data <  1) { return -1; } rest_data += 1; //hop
    if (end - rest_data <  4) { return -1; } rest_data += 4; //ID
    if (end - rest_data <  2) { return -1; } rest_data += 2; //second elapsed
    if (end - rest_data <  2) { return -1; } rest_data += 2; //bootp flags
    if (end - rest_data <  4) { return -1; } rest_data += 4; //ip
    if (end - rest_data <  4) { return -1; } rest_data += 4; //ip
    if (end - rest_data <  4) { return -1; } rest_data += 4; //ip
    if (end - rest_data <  4) { return -1; } rest_data += 4; //ip
    if (end - rest_data < 16) { return -1; } memcpy(((uint8_t *)&mac_l) + 2, rest_data, 6); mac_l = be64toh(mac_l); rest_data +=16; //mac
    if (end - rest_data < 64) { return -1; } rest_data +=64; //server host name
    if (end - rest_data <128) { return -1; } rest_data +=128; //boot file
    if (end - rest_data <  4) { return -1; } rest_data += 4; //magic cookie
    while (end - rest_data > 2) {
        uint8_t opt_num = rest_data[0];
        uint8_t n = rest_data[1];
        rest_data += 2;
        if (end - rest_data < n) return -1;
        if (opt_num == 0x0c) {
            if (n <= 0) return -1;
            mac2name_add(mac2name, mac_l, (const char *)rest_data, n, 1, verbose);
            if (n >= *hostname_buf_len) { LOGMSG("parsed out %ubyte, [caller at %s %u]\n", n, caller_func, caller_line); return -1; }
            memcpy(hostname, rest_data, n);
            if (callback) callback((const char *)rest_data, n, mac_l, c);
            return 0;
        }
        rest_data += n;
    }
    return 0;
}

static inline int mac2name_dump_stat(struct mac2name *mac2name, char *buf, int len)
{
    if (len <= 0) return 0;
    int ret = snprintf(buf, len-1, " mac2name_pool_size %u mac2name_timeout %us rest %"PRIu64"s mac2name_clear_count %u", mac2name->mac2name_pool_size, mac2name->mac2name_timeout, mac2name->mac2name_timeout_last + mac2name->mac2name_timeout - (uint64_t)time(NULL), mac2name->mac2name_clear_count);
    buf[len - 1] = 0;
    if (ret > len -1) ret = len -1;
    return ret;
}

typedef struct mac2name_hostname_node {
    char *buf;
    int len;
    struct list_head node;
} max2name_hostname_node_t;

//static inline int dhcp_hostname_add(char *hostname, int hostname_len, struct list_head *dhcp_hostname_list)
#define dhcp_hostname_add(hostname, hostname_len, dhcp_hostname_list) ({ \
    hostname_node_t *mac2name_hostname_node = calloc(1, sizeof(hostname_node_t)); \
    if (mac2name_hostname_node) { \
        mac2name_hostname_node->buf = malloc(hostname_len); if (mac2name_hostname_node->buf == NULL) { free(mac2name_hostname_node); mac2name_hostname_node = NULL; } \
        if (mac2name_hostname_node && mac2name_hostname_node->buf) { \
            memcpy(mac2name_hostname_node->buf, hostname, hostname_len); \
            mac2name_hostname_node->len = hostname_len; \
            list_add_tail(&mac2name_hostname_node->node, dhcp_hostname_list); \
        } \
    } \
    0; })

static inline void dhcp_hostname_list_free(struct list_head *head)
{
    hostname_node_t *_p, *_n; \
        list_for_each_entry_safe(_p, _n, head, node) { \
            list_del(&_p->node); \
                free(_p->buf); \
                free(_p); \
        } \
}

#endif
